/******************************************************************************
 * * hal_ble_hr.c - ble profile for heart rate
 *
 * *(C) Copyright 2021 Asr International Ltd.
 * * All Rights Reserved
 * ******************************************************************************/

/*********************
 *      INCLUDES
 *********************/

#include "hal_ble_profile.h"

#if USE_LV_BLE_DEMO != 0

/*********************
 *      DEFINES
 *********************/
#define BLE_FEATURE_SENSOR_CONTACT        (1)
#define BLE_FEATURE_ENERGY_EXPENDED       (1)
#define BLE_FEATURE_RR_INTERVAL           (1)
#define BLE_FEATURE_BODY_SENSOR_LOCATION  (1)

#define BLE_ENERGY_EXPENDED_RPT_INTERVAL  (10)     /* Energy Expended field is included in
                                                    * the Heart Rate Measurement characteristic
                                                    * once every 10 measurements */

#define BLE_MAX_RR_INTERVAL_NUM           (20)     /* temporary value for test */

#define BLE_NO_SKIN_CONTACT_TIMER_LEN     (20 * 1000)

#define BLE_MAX_MANUFACTURER_NAME_LEN     (23 - 4) /* temporary value for test */

#define BLE_MANUFACTURER_NAME             ("ASR")

/**********************
 *      TYPEDEFS
 **********************/
typedef struct 
{
    void * no_skin_contact_timer;
    void * notify_timer;
    uint8_t data[20];
    uint16_t meas_value;
    uint16_t notify_config;
    bool is_skin_contact; /* false if the device detects no or poor contact with the skin,
                           * otherwise true */
#if BLE_FEATURE_ENERGY_EXPENDED != 0
    uint16_t energy_expended;
    uint8_t meas_count;
#endif
#if BLE_FEATURE_RR_INTERVAL != 0
    uint16_t rr_interval_val[BLE_MAX_RR_INTERVAL_NUM];
#endif
#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
    uint8_t body_sensor_loc;
#endif

    /* heart rate service */
    struct le_uuid_16 uuid_heart_rate;
    struct le_uuid_16 uuid_meas;
    struct gatt_chrc chrc_meas;
#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
    struct le_uuid_16 uuid_loc;
    struct gatt_chrc chrc_loc;
#endif
#if BLE_FEATURE_ENERGY_EXPENDED != 0
    struct le_uuid_16 uuid_ctrl_point;
    struct gatt_chrc chrc_ctrl_point;
#endif

    /* device information service */
    struct le_uuid_16 uuid_dev_info;
    struct le_uuid_16 uuid_name;
    struct gatt_chrc chrc_name;

    /* for demo test begin */
    char mfr_name[BLE_MAX_MANUFACTURER_NAME_LEN + 1];
    int16_t meas_chg_val;
    void * meas_test_timer;
    /* for demo test end */
} ble_hrp_global_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void ble_hrp_deinit(void);
static uint32_t ble_hrp_get_service(struct gatt_attr * attrs);
static void ble_hrp_test_process(void);
static int32_t ble_hrp_att_event_handle(struct bt_task_event * msg);
static int32_t ble_hrp_meas_notify_cb(void * param);
#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
static int32_t ble_hrp_loc_read_cb(void * param);
#endif
#if BLE_FEATURE_ENERGY_EXPENDED != 0
static int32_t ble_hrp_ctrl_point_write_cb(void * param);
#endif
static int32_t ble_hrp_name_write_cb(void * param);
static int32_t ble_hrp_name_read_cb(void * param);

/**********************
 *  STATIC VARIABLES
 **********************/
static ble_hrp_global_t * hrp_global = NULL;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
***********************/
void ble_hrp_init(void)
{
    if(hrp_global) {
        printf("%s: warning, hrp has been initiated\n", __FUNCTION__);
        return;
    }

    hrp_global = Hal_Mem_Alloc(sizeof(ble_hrp_global_t));
    Hal_Mem_Set(hrp_global, 0, sizeof(ble_hrp_global_t));

    Hal_Mem_Copy(hrp_global->mfr_name, BLE_MANUFACTURER_NAME, strlen(BLE_MANUFACTURER_NAME));

    /* heart rate service */
    hrp_global->uuid_heart_rate.uuid.type = LE_UUID_TYPE_16;
    hrp_global->uuid_heart_rate.value = LE_ATT_UUID_HEART_RATE;
    hrp_global->uuid_meas.uuid.type = LE_UUID_TYPE_16;
    hrp_global->uuid_meas.value = LE_ATT_UUID_HEART_RATE_MEASUREMENT;
    hrp_global->chrc_meas.uuid = LE_ATT_UUID_HEART_RATE_MEASUREMENT;
    hrp_global->chrc_meas.properties = LE_ATT_CHARC_PROP_NOTIFY;
#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
    hrp_global->uuid_loc.uuid.type = LE_UUID_TYPE_16;
    hrp_global->uuid_loc.value = LE_ATT_UUID_BODY_SENSOR_LOCATION;
    hrp_global->chrc_loc.uuid = LE_ATT_UUID_BODY_SENSOR_LOCATION;
    hrp_global->chrc_loc.properties = LE_ATT_CHARC_PROP_READ;
#endif
#if BLE_FEATURE_ENERGY_EXPENDED != 0
    hrp_global->uuid_ctrl_point.uuid.type = LE_UUID_TYPE_16;
    hrp_global->uuid_ctrl_point.value = LE_ATT_UUID_HEART_RATE_CONTROL_POINT;
    hrp_global->chrc_ctrl_point.uuid = LE_ATT_UUID_HEART_RATE_CONTROL_POINT;
    hrp_global->chrc_ctrl_point.properties = LE_ATT_CHARC_PROP_WRITE;
#endif

    /* device information service */
    hrp_global->uuid_dev_info.uuid.type = LE_UUID_TYPE_16;
    hrp_global->uuid_dev_info.value = LE_ATT_UUID_DEVICE_INFO;
    hrp_global->uuid_name.uuid.type = LE_UUID_TYPE_16;
    hrp_global->uuid_name.value = LE_ATT_UUID_MANUFACTURER_NAME;
    hrp_global->chrc_name.uuid = LE_ATT_UUID_MANUFACTURER_NAME;
    hrp_global->chrc_name.properties = LE_ATT_CHARC_PROP_READ | LE_ATT_CHARC_PROP_WRITE;

    ble_profile_cb_reg(BLE_PROFILE_HEART_RATE, ble_hrp_get_service,
                       ble_hrp_att_event_handle, ble_hrp_deinit);

    ble_hrp_test_process();
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static uint32_t ble_hrp_get_service(struct gatt_attr * attrs)
{
    if(!hrp_global) {
        printf("%s: warning, hrp is not initiated\n", __FUNCTION__);
        return 0;
    }

    printf("%s\n", __FUNCTION__);

    struct gatt_attr serv_attrs[] = {
        /* heart rate service */
        LE_DECLARE_PRIMARY_SERVICE(hrp_global->uuid_heart_rate, LE_UUID_TYPE_16),
        LE_DECLARE_CHARACTERISTIC(hrp_global->chrc_meas, hrp_global->uuid_meas,
                                  LE_ATT_PM_READABLE, NULL, NULL, NULL, 0),
        LE_DECLARE_CLINET_CHRAC_CONFIG(hrp_global->notify_config, ble_hrp_meas_notify_cb),
#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
        LE_DECLARE_CHARACTERISTIC(hrp_global->chrc_loc, hrp_global->uuid_loc, LE_ATT_PM_READABLE,
                                  NULL, ble_hrp_loc_read_cb, NULL, 0),
#endif
#if BLE_FEATURE_ENERGY_EXPENDED != 0
        LE_DECLARE_CHARACTERISTIC(hrp_global->chrc_ctrl_point, hrp_global->uuid_ctrl_point,
                                  LE_ATT_PM_READABLE | LE_ATT_PM_WRITEABLE,
                                  ble_hrp_ctrl_point_write_cb, NULL, NULL, 0),
#endif

        /* device information service */
        LE_DECLARE_PRIMARY_SERVICE(hrp_global->uuid_dev_info, LE_UUID_TYPE_16),
        LE_DECLARE_CHARACTERISTIC(hrp_global->chrc_name, hrp_global->uuid_name,
                                  LE_ATT_PM_READABLE | LE_ATT_PM_WRITEABLE,
                                  ble_hrp_name_write_cb, ble_hrp_name_read_cb, NULL, 0),
    };

    uint32_t size = sizeof(serv_attrs);
    printf("%s: size %d, attrs %d\n", __FUNCTION__, size, attrs);
    if(attrs) {
        Hal_Mem_Copy(attrs, serv_attrs, size);
    }

    return size;
}

static int32_t ble_hrp_att_event_handle(struct bt_task_event * msg)
{
    switch(msg->event_id) {
    case BTTASK_IND_LE_GATT_DISCONNECTED:
        if(hrp_global->notify_timer) {
            uos_timer_delete(hrp_global->notify_timer);
            hrp_global->notify_timer = NULL;
        }
        if(hrp_global->no_skin_contact_timer) {
            uos_timer_delete(hrp_global->no_skin_contact_timer);
            hrp_global->no_skin_contact_timer = NULL;
        }
        Hal_Mem_Set(hrp_global->data, 0, 20);
        hrp_global->notify_config = 0;
#if BLE_FEATURE_ENERGY_EXPENDED != 0
        hrp_global->energy_expended = 0;
        hrp_global->meas_count = 0;
#endif
        break;

    default:
        break;
    }
    return 0;
}

static void ble_hrp_deinit(void)
{
    if(NULL == hrp_global) {
        printf("%s: warning, hrp_global is NULL\n", __FUNCTION__);
        return;
    }

    printf("%s\n", __FUNCTION__);

    if(hrp_global->notify_timer) {
        uos_timer_delete(hrp_global->notify_timer);
    }
    if(hrp_global->no_skin_contact_timer) {
        uos_timer_delete(hrp_global->no_skin_contact_timer);
    }

    /* for demo test begin */
    if(hrp_global->meas_test_timer) {
        uos_timer_delete(hrp_global->meas_test_timer);
    }
    /* for demo test end */

    Hal_Mem_Free(hrp_global);
    hrp_global = NULL;
}

static void ble_hrp_meas_notify_timer_cb(uint32_t arg)
{
    bool is_energy_expended = false;

    // reset heart rate measurement data
    Hal_Mem_Set(hrp_global->data, 0, 20);
    uint8_t i = 1;
    if(0xFF < hrp_global->meas_value) {
        // the Heart Rate Value format is in a UINT16 format
        hrp_global->data[0] |= 0x01;
        hrp_global->data[i++] = hrp_global->meas_value >> 8;
        hrp_global->data[i++] = hrp_global->meas_value & 0x00FF;
    } else {
        // the Heart Rate Value format is in a UINT8 format
        hrp_global->data[i] = (uint8_t)hrp_global->meas_value;
        i += sizeof(uint8_t);
    }
#if BLE_FEATURE_SENSOR_CONTACT != 0
    hrp_global->data[0] |= 0x01 << 2;
    if(hrp_global->is_skin_contact) {
        hrp_global->data[0] |= 0x01 << 1;
    }
#endif
#if BLE_FEATURE_ENERGY_EXPENDED != 0
    hrp_global->meas_count++;
    if(BLE_ENERGY_EXPENDED_RPT_INTERVAL == hrp_global->meas_count) {
        is_energy_expended = true;
        hrp_global->meas_count = 0;
        hrp_global->data[0] |= 0x01 << 3;
        hrp_global->data[i++] = hrp_global->energy_expended & 0x00FF;
        hrp_global->data[i++] = hrp_global->energy_expended >> 8;
    }
#endif
#if BLE_FEATURE_RR_INTERVAL != 0
    if(0 != hrp_global->rr_interval_val[0]) {
        hrp_global->data[0] |= 0x01 << 4;
        uint8_t rpt_count = 8;
        if(0xFF < hrp_global->meas_value) {
            rpt_count = 7;
        }
        if(false == is_energy_expended) {
            rpt_count++;
        }
        printf("%s: rr interval rpt count %d\n", __FUNCTION__, rpt_count);
        uint8_t j;
        for(j = 0;j < rpt_count;j++) {
            if(0 == hrp_global->rr_interval_val[j]) break;
            hrp_global->data[i++] = hrp_global->rr_interval_val[j] & 0x00FF;
            hrp_global->data[i++] = hrp_global->rr_interval_val[j] >> 8;
            hrp_global->rr_interval_val[j] = 0;
        }
        if(rpt_count == j) {
            for(;j < BLE_MAX_RR_INTERVAL_NUM;j++) {
                if(0 == hrp_global->rr_interval_val[j]) break;
                hrp_global->rr_interval_val[j - rpt_count] = hrp_global->rr_interval_val[j];
                hrp_global->rr_interval_val[j] = 0;
            }
        }
    }
#endif

    printf("%s: handle %d, data len %d, meas value %d\n",
           __FUNCTION__, hrp_global->chrc_meas.handle, i, hrp_global->meas_value);
    appbt_le_notify(hrp_global->chrc_meas.handle, hrp_global->data, i);
}

static int32_t ble_hrp_meas_notify_cb(void * param)
{
    printf("%s: notify_config %d\n", __FUNCTION__, hrp_global->notify_config);
    if(0 == hrp_global->notify_config) {
        if(hrp_global->notify_timer) {
            uos_timer_delete(hrp_global->notify_timer);
            hrp_global->notify_timer = NULL;
        }
    } else if(LE_CCC_BIT_NOTIFIED == (hrp_global->notify_config & LE_CCC_BIT_NOTIFIED)) {
        if(!hrp_global->notify_timer) {
#if BLE_FEATURE_ENERGY_EXPENDED != 0
            hrp_global->meas_count = 0;
#endif
            uos_timer_create(&hrp_global->notify_timer);
            uos_timer_start(hrp_global->notify_timer, MS_TO_TICKS(1000), MS_TO_TICKS(1000),
                            ble_hrp_meas_notify_timer_cb, 0);
        } else {
            printf("%s: notify_timer is running\n", __FUNCTION__);
        }
    }
    return 0;
}

#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
static int32_t ble_hrp_loc_read_cb(void * param)
{
    struct gatt_attr_rw_parameters * rw = (struct gatt_attr_rw_parameters *)param;
    uint8_t len = sizeof(uint8_t);
    rw->data = malloc(len); // not use lv_mem_alloc or Hal_Mem_Alloc
    rw->data[0] = hrp_global->body_sensor_loc;
    return len;
}
#endif

#if BLE_FEATURE_ENERGY_EXPENDED != 0
static int32_t ble_hrp_ctrl_point_write_cb(void * param)
{
    struct gatt_attr_rw_parameters *rw = (struct gatt_attr_rw_parameters *)param;
    printf("%s: handle %04x, length %d, offset %d\n",
           __FUNCTION__, rw->handle, rw->length, rw->offset);
    for(uint16_t i = 0; i < rw->length; i++) {
        printf("%s: %d(%02x)\n", __FUNCTION__, rw->data[i], rw->data[i]);
    }
    uint16_t ctrl_point;
    if(sizeof(uint8_t) < rw->length) {
        return LE_ERR_CONTROL_POINT_NOT_SUPP;
    } else {
        ctrl_point = rw->data[0];
    }
    if(0x01 == ctrl_point) {
        hrp_global->energy_expended = 0;
    } else {
        printf("%s: invalid ctrl_point %d\n", __FUNCTION__, ctrl_point);
        return LE_ERR_CONTROL_POINT_NOT_SUPP;
    }
    return LE_ERR_OK;
}
#endif

static int32_t ble_hrp_name_write_cb(void * param)
{
    struct gatt_attr_rw_parameters *rw = (struct gatt_attr_rw_parameters *)param;
    printf("%s: handle %04x, length %d, offset %d\n",
           __FUNCTION__, rw->handle, rw->length, rw->offset);
    for(uint16_t i = 0; i < rw->length; i++) {
        printf("%s: %d(%02x)\n", __FUNCTION__, rw->data[i], rw->data[i]);
    }
    // update to local memory
    Hal_Mem_Set(hrp_global->mfr_name, 0, BLE_MAX_MANUFACTURER_NAME_LEN + 1);
    uint8_t len = (BLE_MAX_MANUFACTURER_NAME_LEN > rw->length) ? rw->length : BLE_MAX_MANUFACTURER_NAME_LEN;
    Hal_Mem_Copy(hrp_global->mfr_name, rw->data, len);
    printf("%s: new name is %s\n", __FUNCTION__, hrp_global->mfr_name);
    return LE_ERR_OK;
}

static int32_t ble_hrp_name_read_cb(void * param)
{
    struct gatt_attr_rw_parameters * rw = (struct gatt_attr_rw_parameters *)param;
    uint8_t len = strlen(hrp_global->mfr_name);
    printf("%s: handle %d, name len %d, %s\n", __FUNCTION__, rw->handle, len, hrp_global->mfr_name);
    rw->data = malloc(len); // not use lv_mem_alloc or Hal_Mem_Alloc
    memset(rw->data, 0, len);
    memcpy(rw->data, hrp_global->mfr_name, len);
    return len;
}

static void ble_hrp_no_skin_contact_timer_cb(uint32_t arg)
{
    if(BLE_STATE_CONNECTED != ble_get_state()) {
        printf("%s: not connected\n", __FUNCTION__);
        return;
    }

    printf("%s: disconnect when out of contact with skin for long time\n", __FUNCTION__);

    appbt_le_disconnect();

    uos_timer_delete(hrp_global->no_skin_contact_timer);
    hrp_global->no_skin_contact_timer = NULL;
}

/* used to get the status of the skin contact */
static void ble_hrp_skin_contact_cb(uint8_t param)
{
    printf("%s: skin contact status, cur %d, new %d\n",
           __FUNCTION__, hrp_global->is_skin_contact, param);
    if(0 == param) {
        if(hrp_global->is_skin_contact) {
            hrp_global->is_skin_contact = false;
            if((NULL == hrp_global->no_skin_contact_timer)
               && (BLE_STATE_CONNECTED == ble_get_state())) {
                printf("%s: start no_skin_contact_timer\n", __FUNCTION__);
                uos_timer_create(&hrp_global->no_skin_contact_timer);
                uos_timer_start(hrp_global->no_skin_contact_timer, MS_TO_TICKS(BLE_NO_SKIN_CONTACT_TIMER_LEN),
                                0, ble_hrp_no_skin_contact_timer_cb, 0);
            }
        }
    } else {
        if(false == hrp_global->is_skin_contact) {
            hrp_global->is_skin_contact = true;
            if(hrp_global->no_skin_contact_timer && (BLE_STATE_CONNECTED == ble_get_state())) {
                printf("%s: stop no_skin_contact_timer\n", __FUNCTION__);
                uos_timer_delete(hrp_global->no_skin_contact_timer);
                hrp_global->no_skin_contact_timer = NULL;
            }
        }
    }
}

static void ble_hrp_meas_test_timer_cb(uint32_t arg)
{
    hrp_global->meas_value += hrp_global->meas_chg_val;
    if(100 == hrp_global->meas_value) {
        hrp_global->meas_chg_val = -10;
    } else if(0 == hrp_global->meas_value) {
        hrp_global->meas_chg_val = 10;
    }

#if BLE_FEATURE_RR_INTERVAL != 0
    hrp_global->rr_interval_val[0] = 2;
    hrp_global->rr_interval_val[1] = 3;
    hrp_global->rr_interval_val[2] = 4;
    hrp_global->rr_interval_val[3] = 5;
    hrp_global->rr_interval_val[4] = 6;
    hrp_global->rr_interval_val[5] = 7;
    hrp_global->rr_interval_val[6] = 8;
    hrp_global->rr_interval_val[7] = 9;
    hrp_global->rr_interval_val[8] = 10;
#endif

#if BLE_FEATURE_ENERGY_EXPENDED != 0
    if(256 > hrp_global->energy_expended) {
        hrp_global->energy_expended += 20;
    } else if((65535 - 999) > hrp_global->energy_expended) {
        hrp_global->energy_expended += 1000;
    } else {
        hrp_global->energy_expended = 0xFFFF;
    }

    printf("%s: meas_value %d, energy_expended %d\n", __FUNCTION__,
           hrp_global->meas_value, hrp_global->energy_expended);
#else
    printf("%s: meas_value %d\n", __FUNCTION__, hrp_global->meas_value);
#endif
}

static void ble_hrp_test_process(void)
{
    // for demo test
    hrp_global->meas_chg_val = 10;
    hrp_global->meas_value = 0;
    uos_timer_create(&hrp_global->meas_test_timer);
    uos_timer_start(hrp_global->meas_test_timer, MS_TO_TICKS(1000), MS_TO_TICKS(1000),
                    ble_hrp_meas_test_timer_cb, 0);
    hrp_global->rr_interval_val[0] = 2;
    hrp_global->rr_interval_val[1] = 3;
    hrp_global->rr_interval_val[2] = 4;
    hrp_global->rr_interval_val[3] = 5;
    hrp_global->rr_interval_val[4] = 6;
    hrp_global->rr_interval_val[5] = 7;
    hrp_global->rr_interval_val[6] = 8;
    hrp_global->rr_interval_val[7] = 9;
    hrp_global->rr_interval_val[8] = 10;

#if BLE_FEATURE_SENSOR_CONTACT != 0
    ble_hrp_skin_contact_cb(1);
#endif

#if BLE_FEATURE_BODY_SENSOR_LOCATION != 0
    hrp_global->body_sensor_loc = 0x01;
#endif
}

#endif
